package pl.com.qapps.datastore.berkeley.impl;

import java.io.IOException;

import pl.com.qapps.datastore.BytesHelper;
import pl.com.qapps.datastore.DatastoreUtils;
import pl.com.qapps.datastore.IdStore;

import com.google.appengine.api.datastore.Key;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.Environment;
import com.sleepycat.je.OperationStatus;

public class BerkeleyIdStore extends StoreTable implements IdStore {

	private static final DatabaseEntry ID_DATA_ONE = new DatabaseEntry(
			new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 });
	private static final DatabaseEntry ID_DATA_ZERO = new DatabaseEntry(
			new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 });
	private static final DatabaseEntry ROOT = new DatabaseEntry(
			new byte[] { 0 });

	private final Database db;

	public BerkeleyIdStore(Environment env, String filename) {
		super(env);

		DatabaseConfig cfg = new DatabaseConfig();
		cfg.setAllowCreate(true);
		cfg.setTransactional(true);
		cfg.setSortedDuplicates(false);
		// cfg.setBtreeComparator(COMPARATOR);

		db = env.openDatabase(null, filename, cfg);
	}

	@Override
	public void close() throws IOException {
		db.close();
	}

	// TODO needs better concurrency control, transactions maybe.
	// TODO implement storing key using kind
	@Override
	public synchronized long next(Key parent, long amount) throws IOException {
		DatabaseEntry keyData;
		DatabaseEntry idData;

		if (parent == null) {
			// root entity id
			keyData = ROOT;
		} else {
			// entity group id
			byte[] rootBytes = BytesHelper.serialize(DatastoreUtils
					.getEntityGroup(parent));
			keyData = new DatabaseEntry(rootBytes);
		}

		// get last id
		idData = getLastId(keyData);
		long lastId = BytesHelper.toLong(idData.getData());

		lastId += amount;

		// update new counter value
		db.put(null, keyData, new DatabaseEntry(BytesHelper.serialize(lastId)));

		return lastId;
	}

	/**
	 * Fetches last used id for specified group. If there isn't one, creates it
	 * automatically.
	 * 
	 * @param keyData
	 * @return last used id
	 */
	private DatabaseEntry getLastId(DatabaseEntry keyData) {
		DatabaseEntry lastIdData = new DatabaseEntry();

		OperationStatus status = db.get(null, keyData, lastIdData, null);
		if (status == OperationStatus.NOTFOUND) {
			// initialize id counter for roots or any entity group
			lastIdData = ID_DATA_ZERO;
			db.put(null, keyData, lastIdData);
		}

		return lastIdData;
	}
}
